import type { ChildProcess } from 'child_process'
import { EventEmitter } from 'events'
import { kill } from './kill'
import pidusage from 'pidusage'

type ProcessInfo = pidusage.Status

interface ProcessManagerEvents {
  start: () => void
  stop: () => void
  exit: (info: { code: number | null; signal: NodeJS.Signals | null }) => void
  close: (info: { code: number | null; signal: NodeJS.Signals | null }) => void
  error: (err: Error) => void
}

class ProcessManager extends EventEmitter {
  private processFactory: () => ChildProcess
  private process: ChildProcess | null = null
  private running: boolean = false

  constructor(processFactory: () => ChildProcess) {
    super()
    this.processFactory = processFactory
  }

  async start(): Promise<void> {
    return new Promise((resolve, reject) => {
      if (this.running) {
        return reject(new Error('Process is already running.'))
      }

      let started = false

      this.process = this.processFactory()

      if (this.process.pid) {
        started = true
      }

      this.process.on('error', (err) => {
        if (!started) {
          this.running = false
          reject(err)
        }
        this.emit('error', err)
      })

      this.process.on('exit', (code, signal) => {
        this.emit('exit', { code, signal })
      })

      this.process.on('close', (code, signal) => {
        this.running = false
        this.emit('close', { code, signal })
      })

      if (started) {
        this.running = true
        this.emit('start')
        resolve()
      }
    })
  }

  async stop(): Promise<void> {
    if (!this.process) return
    return await kill(this.process)
  }

  async restart(): Promise<void> {
    await this.stop()
    await this.start()
  }

  async restartIfNotRunning(): Promise<void> {
    if (!this.isRunning) {
      await this.restart()
    }
  }

  async getInfo(): Promise<ProcessInfo> {
    if (!this.process || !this.process.pid) {
      throw new Error('Process is not running.')
    }

    return await pidusage(this.process.pid)
  }

  kill(signal: NodeJS.Signals = 'SIGTERM'): void {
    if (this.process) {
      this.process.kill(signal)
    }
  }

  get pid() {
    if (!this.childProcess) return null
    return this.childProcess.pid
  }

  get isRunning(): boolean {
    return this.running
  }

  get childProcess(): ChildProcess | null {
    return this.process
  }

  on<K extends keyof ProcessManagerEvents>(event: K, listener: ProcessManagerEvents[K]): this {
    return super.on(event, listener)
  }

  once<K extends keyof ProcessManagerEvents>(event: K, listener: ProcessManagerEvents[K]): this {
    return super.once(event, listener)
  }

  off<K extends keyof ProcessManagerEvents>(event: K, listener: ProcessManagerEvents[K]): this {
    return super.off(event, listener)
  }

  emit<K extends keyof ProcessManagerEvents>(
    event: K,
    ...args: Parameters<ProcessManagerEvents[K]>
  ): boolean {
    return super.emit(event, ...args)
  }
}

export { ProcessManager, ProcessManagerEvents, ProcessInfo }
